"""
Pagination utilities following SOLID principles.
"""

from typing import List, Optional, Generic, TypeVar, Any
from math import ceil
from pydantic import BaseModel, Field
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession

T = TypeVar('T')


class PaginationParams(BaseModel):
    """Pagination parameters."""
    
    page: int = Field(default=1, ge=1, description="Page number")
    size: int = Field(default=20, ge=1, le=100, description="Page size")
    
    @property
    def offset(self) -> int:
        """Calculate offset for database queries."""
        return (self.page - 1) * self.size


class PaginationResponse(BaseModel, Generic[T]):
    """Pagination response wrapper."""
    
    items: List[T] = Field(description="List of items")
    total: int = Field(description="Total number of items")
    page: int = Field(description="Current page number")
    size: int = Field(description="Page size")
    pages: int = Field(description="Total number of pages")
    has_next: bool = Field(description="Whether there is a next page")
    has_prev: bool = Field(description="Whether there is a previous page")
    
    @classmethod
    def create(
        cls,
        items: List[T],
        total: int,
        page: int,
        size: int
    ) -> 'PaginationResponse[T]':
        """Create pagination response."""
        pages = ceil(total / size) if size > 0 else 0
        
        return cls(
            items=items,
            total=total,
            page=page,
            size=size,
            pages=pages,
            has_next=page < pages,
            has_prev=page > 1
        )


class Paginator:
    """Database pagination utility."""
    
    @staticmethod
    async def paginate_query(
        db: AsyncSession,
        query,
        params: PaginationParams,
        count_query: Optional[Any] = None
    ) -> PaginationResponse:
        """
        Paginate SQLAlchemy query.
        
        Args:
            db: Database session
            query: SQLAlchemy query to paginate
            params: Pagination parameters
            count_query: Optional custom count query
        """
        # Get total count
        if count_query is not None:
            total_result = await db.execute(count_query)
            total = total_result.scalar()
        else:
            # Create count query from original query
            count_query = select(func.count()).select_from(query.subquery())
            total_result = await db.execute(count_query)
            total = total_result.scalar()
        
        # Get paginated items
        paginated_query = query.offset(params.offset).limit(params.size)
        result = await db.execute(paginated_query)
        items = result.scalars().all()
        
        return PaginationResponse.create(
            items=items,
            total=total,
            page=params.page,
            size=params.size
        )
    
    @staticmethod
    def paginate_list(
        items: List[T],
        params: PaginationParams
    ) -> PaginationResponse[T]:
        """
        Paginate a list in memory.
        
        Args:
            items: List to paginate
            params: Pagination parameters
        """
        total = len(items)
        start_index = params.offset
        end_index = start_index + params.size
        
        paginated_items = items[start_index:end_index]
        
        return PaginationResponse.create(
            items=paginated_items,
            total=total,
            page=params.page,
            size=params.size
        )
    
    @staticmethod
    async def paginate_async_generator(
        generator,
        params: PaginationParams,
        total_count: Optional[int] = None
    ) -> PaginationResponse:
        """
        Paginate an async generator.
        
        Args:
            generator: Async generator to paginate
            params: Pagination parameters
            total_count: Optional total count (if known)
        """
        items = []
        current_index = 0
        start_index = params.offset
        end_index = start_index + params.size
        
        async for item in generator:
            if current_index >= start_index and current_index < end_index:
                items.append(item)
            
            current_index += 1
            
            # Stop if we have enough items
            if len(items) >= params.size:
                break
        
        # If total_count is not provided, we can't calculate exact pagination info
        if total_count is None:
            total_count = current_index
        
        return PaginationResponse.create(
            items=items,
            total=total_count,
            page=params.page,
            size=params.size
        )


class CursorPagination(BaseModel):
    """Cursor-based pagination for large datasets."""
    
    cursor: Optional[str] = Field(default=None, description="Cursor for next page")
    size: int = Field(default=20, ge=1, le=100, description="Page size")


class CursorPaginationResponse(BaseModel, Generic[T]):
    """Cursor pagination response."""
    
    items: List[T] = Field(description="List of items")
    next_cursor: Optional[str] = Field(description="Cursor for next page")
    has_next: bool = Field(description="Whether there is a next page")
    
    @classmethod
    def create(
        cls,
        items: List[T],
        next_cursor: Optional[str] = None
    ) -> 'CursorPaginationResponse[T]':
        """Create cursor pagination response."""
        return cls(
            items=items,
            next_cursor=next_cursor,
            has_next=next_cursor is not None
        )


class OffsetPagination:
    """Offset-based pagination helper."""
    
    @staticmethod
    def calculate_pagination_info(
        total: int,
        page: int,
        size: int
    ) -> dict:
        """Calculate pagination information."""
        pages = ceil(total / size) if size > 0 else 0
        
        return {
            'total': total,
            'page': page,
            'size': size,
            'pages': pages,
            'has_next': page < pages,
            'has_prev': page > 1,
            'next_page': page + 1 if page < pages else None,
            'prev_page': page - 1 if page > 1 else None,
            'offset': (page - 1) * size
        }
    
    @staticmethod
    def get_page_numbers(
        current_page: int,
        total_pages: int,
        max_pages: int = 10
    ) -> List[int]:
        """Get list of page numbers to display."""
        if total_pages <= max_pages:
            return list(range(1, total_pages + 1))
        
        # Calculate start and end pages
        half_max = max_pages // 2
        
        if current_page <= half_max:
            return list(range(1, max_pages + 1))
        elif current_page > total_pages - half_max:
            return list(range(total_pages - max_pages + 1, total_pages + 1))
        else:
            return list(range(current_page - half_max, current_page + half_max + 1))